// © Michael_Fx_Trader — All rights reserved.
//
// ============================================================================================
// ORIGINAL SCRIPT DECLARATION / AUTHOR VERIFICATION
// ============================================================================================
// Script Title      : ICT SMC key Levels Execution
// Author            : Michael_Fx_Trader
// Publisher         : Michael_Fx_Trader
// Original Work     : YES. This script is an original, from-scratch implementation written
//                      entirely by Michael_Fx_Trader. It is NOT a copy, re-upload, decompiled
//                      clone, or re-skin of any other author's protected or open-source script.
//                      No third-party source code has been copied, translated line-by-line, or
//                      obfuscated/renamed to disguise its origin.
// Concept Basis     : The script implements well-established, publicly documented Smart Money
//                      Concepts / ICT trading theory (structure, fair value gaps, order blocks,
//                      liquidity sweeps, session timing, previous day levels, market structure
//                      shifts). These are general, non-proprietary trading concepts described in
//                      widely available public trading education material. All detection logic,
//                      state management, drawing code, and visual design in this script were
//                      independently written by the author using Pine Script v6 native functions.
// Rights            : All rights to this specific implementation belong to Michael_Fx_Trader.
// Declaration       : The author confirms this publication complies with TradingView House
//                      Rules on script originality and does not mimic, mash up, or rehash any
//                      single other author's published script.
// ============================================================================================

//@version=6
indicator("ICT SMC key Levels Execution", shorttitle = "ICT SMC KLE", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500)

// ============================================================================================
// USER-DEFINED TYPES
// ============================================================================================
// A Fair Value Gap zone: an unfilled three-candle imbalance box that is tracked until mitigated.
type FVGZone
    box   zoneBox
    label zoneLabel
    float top
    float bottom
    bool  isBull

// An Order Block zone: the real body of a confirmed swing pivot candle, tracked until invalidated.
type OBZone
    box   zoneBox
    label zoneLabel
    float top
    float bottom
    bool  isBull

// ============================================================================================
// INPUTS
// ============================================================================================

// ---- Structure (Intermediate High / Low) ----
grpStruct        = "Structure  ·  Intermediate High / Low"
structLen        = input.int(15, "Swing Length (Structure)", minval = 2, group = grpStruct, tooltip = "Number of bars checked on each side of a candle to confirm it as a significant swing high/low.")
showStruct       = input.bool(true, "Show Intermediate High / Low Badges", group = grpStruct)
ithColor         = input.color(color.new(color.red, 0),   "Intermediate High Badge Color", group = grpStruct)
itlColor         = input.color(color.new(color.green, 0), "Intermediate Low Badge Color",  group = grpStruct)

// ---- Fair Value Gaps ----
grpFVG           = "Fair Value Gaps"
showFVG          = input.bool(true, "Show Fair Value Gaps", group = grpFVG)
bullFVGColor     = input.color(color.new(color.teal, 80),  "Bullish FVG Fill", group = grpFVG)
bearFVGColor     = input.color(color.new(color.red, 80),   "Bearish FVG Fill", group = grpFVG)
bullFVGBorder    = input.color(color.new(color.teal, 0),   "Bullish FVG Border", group = grpFVG)
bearFVGBorder    = input.color(color.new(color.red, 0),    "Bearish FVG Border", group = grpFVG)
maxFVGCount      = input.int(15, "Max Active FVG Boxes", minval = 1, maxval = 60, group = grpFVG)

// ---- Order Blocks ----
grpOB            = "Order Blocks"
showOB           = input.bool(true, "Show Order Blocks", group = grpOB)
obLen            = input.int(25, "Swing Length (Order Blocks)", minval = 2, group = grpOB, tooltip = "Wider lookback used specifically to hunt for order block origin candles.")
bullOBColor      = input.color(color.new(color.blue, 80),  "Bullish OB Fill", group = grpOB)
bearOBColor      = input.color(color.new(color.orange, 80),"Bearish OB Fill", group = grpOB)
bullOBBorder     = input.color(color.new(color.blue, 0),   "Bullish OB Border", group = grpOB)
bearOBBorder     = input.color(color.new(color.orange, 0), "Bearish OB Border", group = grpOB)
maxOBCount       = input.int(10, "Max Active OB Boxes", minval = 1, maxval = 60, group = grpOB)

// ---- Liquidity Sweeps ----
grpSweep         = "Liquidity Sweeps"
showSweep        = input.bool(true, "Show Liquidity Sweeps", group = grpSweep)
sweepLen         = input.int(10, "Swing Length (Liquidity Sweeps)", minval = 2, group = grpSweep)
sweepBullColor   = input.color(color.new(color.green, 0), "Bullish Sweep Color", group = grpSweep)
sweepBearColor   = input.color(color.new(color.red, 0),   "Bearish Sweep Color", group = grpSweep)

// ---- Market Structure Shift ----
grpMSS           = "Market Structure Shift (MSS)"
showMSS          = input.bool(true, "Show Market Structure Shift", group = grpMSS)
mssLen           = input.int(5, "Swing Length (MSS)", minval = 1, group = grpMSS, tooltip = "More sensitive/short-term lookback used purely for momentum confirmation.")
mssBullColor     = input.color(color.new(color.green, 0), "Bullish MSS Color", group = grpMSS)
mssBearColor     = input.color(color.new(color.red, 0),   "Bearish MSS Color", group = grpMSS)

// ---- New York Session Highlight ----
grpSession       = "New York Session Highlight"
showSession      = input.bool(true, "Highlight New York Session", group = grpSession)
nySession        = input.session("1330-2000", "New York Session Window (Exchange Time, UTC-based)", group = grpSession)
sessionColor     = input.color(color.new(color.teal, 90), "Session Background Color", group = grpSession)

// ---- Previous Day High / Low ----
grpPDHL          = "Previous Day High / Low"
showPDHL         = input.bool(true, "Show Previous Day High / Low", group = grpPDHL)
pdhColor         = input.color(color.new(color.red, 0),   "Previous Day High Color", group = grpPDHL)
pdlColor         = input.color(color.new(color.green, 0), "Previous Day Low Color",  group = grpPDHL)

// ============================================================================================
// GLOBAL STATE VARIABLES
// ============================================================================================

var array<FVGZone> fvgZones = array.new<FVGZone>()
var array<OBZone>  obZones  = array.new<OBZone>()

// Liquidity sweep tracked levels (own dedicated sensitivity, cleared once used).
var float sweepHighVal = na
var int   sweepHighBar = na
var float sweepLowVal  = na
var int   sweepLowBar  = na

// Market structure shift tracked levels (short-term, own dedicated sensitivity).
var float mssHighVal = na
var int   mssHighBar = na
var float mssLowVal  = na
var int   mssLowBar  = na

// Previous day high/low bookkeeping.
var float dayHigh   = na
var float dayLow    = na
var float pdh       = na
var float pdl       = na
var line  pdhLine   = na
var line  pdlLine   = na
var label pdhLabel  = na
var label pdlLabel  = na

// ============================================================================================
// 1) STRUCTURE — INTERMEDIATE HIGH / LOW BADGES
// ============================================================================================
// A candle is confirmed as a significant swing point only once "structLen" bars have printed
// on both sides without breaking it, so these badges always appear retroactively.

structPH = ta.pivothigh(high, structLen, structLen)
structPL = ta.pivotlow(low, structLen, structLen)

if not na(structPH)
    if showStruct
        label.new(bar_index - structLen, structPH, "ITH", style = label.style_label_down, color = ithColor, textcolor = color.white, size = size.small)
    // feed the confirmed swing high into the liquidity sweep tracker (dedicated sweepLen pivots handled separately below)

if not na(structPL)
    if showStruct
        label.new(bar_index - structLen, structPL, "ITL", style = label.style_label_up, color = itlColor, textcolor = color.white, size = size.small)

// ============================================================================================
// 2) FAIR VALUE GAPS (3-candle imbalance)
// ============================================================================================

bullFVGCond = low > high[2]
bearFVGCond = high < low[2]

if bullFVGCond and showFVG
    zoneTop    = low
    zoneBottom = high[2]
    newBox = box.new(bar_index - 2, zoneTop, bar_index, zoneBottom, border_color = bullFVGBorder, bgcolor = bullFVGColor, border_width = 1)
    newLabel = label.new(bar_index, zoneTop, "FVG", style = label.style_label_left, color = color.new(color.white, 100), textcolor = bullFVGBorder, size = size.tiny)
    newZone = FVGZone.new(newBox, newLabel, zoneTop, zoneBottom, true)
    array.push(fvgZones, newZone)
    if array.size(fvgZones) > maxFVGCount
        oldest = array.shift(fvgZones)
        box.delete(oldest.zoneBox)
        label.delete(oldest.zoneLabel)

if bearFVGCond and showFVG
    zoneTop    = low[2]
    zoneBottom = high
    newBox = box.new(bar_index - 2, zoneTop, bar_index, zoneBottom, border_color = bearFVGBorder, bgcolor = bearFVGColor, border_width = 1)
    newLabel = label.new(bar_index, zoneTop, "FVG", style = label.style_label_left, color = color.new(color.white, 100), textcolor = bearFVGBorder, size = size.tiny)
    newZone = FVGZone.new(newBox, newLabel, zoneTop, zoneBottom, false)
    array.push(fvgZones, newZone)
    if array.size(fvgZones) > maxFVGCount
        oldest = array.shift(fvgZones)
        box.delete(oldest.zoneBox)
        label.delete(oldest.zoneLabel)

// Extend active FVG boxes (and their "FVG" tag) to the right edge, and delete any that have been mitigated.
if array.size(fvgZones) > 0
    for i = array.size(fvgZones) - 1 to 0
        zone = array.get(fvgZones, i)
        mitigated = zone.isBull ? close < zone.bottom : close > zone.top
        if mitigated
            box.delete(zone.zoneBox)
            label.delete(zone.zoneLabel)
            array.remove(fvgZones, i)
        else
            box.set_right(zone.zoneBox, bar_index)
            label.set_x(zone.zoneLabel, bar_index)

// ============================================================================================
// 3) ORDER BLOCKS
// ============================================================================================
// Uses its own wider lookback ("obLen") to hunt for confirmed swing pivots, then boxes only the
// real body (open-to-close) of that pivot candle — the wick is treated as a rejected probe.

obPH = ta.pivothigh(high, obLen, obLen)
obPL = ta.pivotlow(low, obLen, obLen)

if not na(obPH) and showOB
    pivotBarIdx = bar_index - obLen
    obTop    = math.max(open[obLen], close[obLen])
    obBottom = math.min(open[obLen], close[obLen])
    newBox = box.new(pivotBarIdx, obTop, bar_index, obBottom, border_color = bearOBBorder, bgcolor = bearOBColor, border_width = 1)
    newLabel = label.new(bar_index, obTop, "ORDER BLOCK", style = label.style_label_left, color = color.new(color.white, 100), textcolor = bearOBBorder, size = size.tiny)
    newZone = OBZone.new(newBox, newLabel, obTop, obBottom, false)
    array.push(obZones, newZone)
    if array.size(obZones) > maxOBCount
        oldest = array.shift(obZones)
        box.delete(oldest.zoneBox)
        label.delete(oldest.zoneLabel)

if not na(obPL) and showOB
    pivotBarIdx = bar_index - obLen
    obTop    = math.max(open[obLen], close[obLen])
    obBottom = math.min(open[obLen], close[obLen])
    newBox = box.new(pivotBarIdx, obTop, bar_index, obBottom, border_color = bullOBBorder, bgcolor = bullOBColor, border_width = 1)
    newLabel = label.new(bar_index, obBottom, "ORDER BLOCK", style = label.style_label_left, color = color.new(color.white, 100), textcolor = bullOBBorder, size = size.tiny)
    newZone = OBZone.new(newBox, newLabel, obTop, obBottom, true)
    array.push(obZones, newZone)
    if array.size(obZones) > maxOBCount
        oldest = array.shift(obZones)
        box.delete(oldest.zoneBox)
        label.delete(oldest.zoneLabel)

// Extend active OB boxes (and their "ORDER BLOCK" tag) to the right edge, and delete any that have
// been invalidated by a close through the wrong side of the zone.
if array.size(obZones) > 0
    for i = array.size(obZones) - 1 to 0
        zone = array.get(obZones, i)
        invalidated = zone.isBull ? close < zone.bottom : close > zone.top
        if invalidated
            box.delete(zone.zoneBox)
            label.delete(zone.zoneLabel)
            array.remove(obZones, i)
        else
            box.set_right(zone.zoneBox, bar_index)
            label.set_x(zone.zoneLabel, bar_index)

// ============================================================================================
// 4) LIQUIDITY SWEEPS
// ============================================================================================
// Tracks the most recent unbroken swing high/low (dedicated "sweepLen" sensitivity). A sweep
// fires when a candle's wick breaches the level intrabar but the close fails to hold beyond it.

sweepPH = ta.pivothigh(high, sweepLen, sweepLen)
sweepPL = ta.pivotlow(low, sweepLen, sweepLen)

if not na(sweepPH)
    sweepHighVal := sweepPH
    sweepHighBar := bar_index - sweepLen

if not na(sweepPL)
    sweepLowVal := sweepPL
    sweepLowBar := bar_index - sweepLen

// Bearish sweep: wick pokes above the tracked high, close fails back below it.
if not na(sweepHighVal) and high > sweepHighVal and close < sweepHighVal
    if showSweep
        line.new(sweepHighBar, sweepHighVal, bar_index, sweepHighVal, color = sweepBearColor, width = 1, style = line.style_solid)
        label.new(bar_index, sweepHighVal, "Liquidity Sweep", style = label.style_label_down, color = sweepBearColor, textcolor = color.white, size = size.tiny)
    sweepHighVal := na
    sweepHighBar := na

// Bullish sweep: wick pokes below the tracked low, close fails back above it.
if not na(sweepLowVal) and low < sweepLowVal and close > sweepLowVal
    if showSweep
        line.new(sweepLowBar, sweepLowVal, bar_index, sweepLowVal, color = sweepBullColor, width = 1, style = line.style_solid)
        label.new(bar_index, sweepLowVal, "Liquidity Sweep", style = label.style_label_up, color = sweepBullColor, textcolor = color.white, size = size.tiny)
    sweepLowVal := na
    sweepLowBar := na

// ============================================================================================
// 5) NEW YORK SESSION HIGHLIGHT
// ============================================================================================

inSession = not na(time(timeframe.period, nySession))
bgcolor(showSession and inSession ? sessionColor : na)

// ============================================================================================
// 6) PREVIOUS DAY HIGH / LOW (self-deleting once touched)
// ============================================================================================

isNewDay = ta.change(time("D")) != 0

if isNewDay
    // Finalize yesterday's completed high/low into the tracked previous-day levels.
    if not na(dayHigh) and not na(dayLow)
        pdh := dayHigh
        pdl := dayLow
        if not na(pdhLine)
            line.delete(pdhLine)
        if not na(pdlLine)
            line.delete(pdlLine)
        if not na(pdhLabel)
            label.delete(pdhLabel)
        if not na(pdlLabel)
            label.delete(pdlLabel)
        if showPDHL
            pdhLine  := line.new(bar_index, pdh, bar_index, pdh, color = pdhColor, width = 1, style = line.style_dashed)
            pdlLine  := line.new(bar_index, pdl, bar_index, pdl, color = pdlColor, width = 1, style = line.style_dashed)
            pdhLabel := label.new(bar_index, pdh, "PDH", style = label.style_label_left, color = pdhColor, textcolor = color.white, size = size.tiny)
            pdlLabel := label.new(bar_index, pdl, "PDL", style = label.style_label_left, color = pdlColor, textcolor = color.white, size = size.tiny)
    dayHigh := high
    dayLow  := low
else
    dayHigh := na(dayHigh) ? high : math.max(dayHigh, high)
    dayLow  := na(dayLow)  ? low  : math.min(dayLow, low)

// Extend active lines to the current bar.
if not na(pdhLine)
    line.set_x2(pdhLine, bar_index)
if not na(pdlLine)
    line.set_x2(pdlLine, bar_index)

// Delete the level the instant price actually trades through it — it is no longer a resting target.
if not na(pdh) and high >= pdh
    line.delete(pdhLine)
    label.delete(pdhLabel)
    pdhLine  := na
    pdhLabel := na
    pdh      := na

if not na(pdl) and low <= pdl
    line.delete(pdlLine)
    label.delete(pdlLabel)
    pdlLine  := na
    pdlLabel := na
    pdl      := na

// ============================================================================================
// 7) MARKET STRUCTURE SHIFT (MSS) — short-term momentum confirmation layer
// ============================================================================================

mssPH = ta.pivothigh(high, mssLen, mssLen)
mssPL = ta.pivotlow(low, mssLen, mssLen)

if not na(mssPH)
    mssHighVal := mssPH
    mssHighBar := bar_index - mssLen

if not na(mssPL)
    mssLowVal := mssPL
    mssLowBar := bar_index - mssLen

// Bullish shift: close crosses up and over the tracked short-term swing high.
if not na(mssHighVal) and close > mssHighVal
    if showMSS
        line.new(mssHighBar, mssHighVal, bar_index, mssHighVal, color = mssBullColor, width = 1, style = line.style_dashed)
        label.new(math.round(math.avg(mssHighBar, bar_index)), mssHighVal, "MSS", style = label.style_label_down, color = color.new(color.white, 100), textcolor = mssBullColor, size = size.tiny)
    mssHighVal := na
    mssHighBar := na

// Bearish shift: close crosses down and under the tracked short-term swing low.
if not na(mssLowVal) and close < mssLowVal
    if showMSS
        line.new(mssLowBar, mssLowVal, bar_index, mssLowVal, color = mssBearColor, width = 1, style = line.style_dashed)
        label.new(math.round(math.avg(mssLowBar, bar_index)), mssLowVal, "MSS", style = label.style_label_up, color = color.new(color.white, 100), textcolor = mssBearColor, size = size.tiny)
    mssLowVal := na
    mssLowBar := na
